package vm

import (
	"errors"
	"go-forth/pkg/forth"
	"io"
	"io/fs"
)

type GoSyscall struct {
	filesystem fs.FS
	dataSpace  *DataSpace
}

func (g *GoSyscall) OpenFile(s string) (int, int) {
	f, err := g.filesystem.Open(s)
	if err != nil {
		switch {
		case errors.Is(err, fs.ErrInvalid):
			return 0, forth.ErrInvalidPath
		case errors.Is(err, fs.ErrNotExist):
			return 0, forth.ErrNonExistentFile
		default:
			return 0, forth.ErrFileIOException
		}
	}
	here := g.dataSpace.Reserved()
	g.dataSpace.Reserve(2)
	g.dataSpace.Store(here, f)
	g.dataSpace.Store(here+1, 0)
	return here, 0
}

func (g *GoSyscall) OpenDir(s string) (int, int) {
	f, err := g.filesystem.Open(s)
	if err != nil {
		switch {
		case errors.Is(err, fs.ErrInvalid):
			return 0, forth.ErrInvalidPath
		case errors.Is(err, fs.ErrNotExist):
			return 0, forth.ErrNonExistentFile
		default:
			return 0, forth.ErrFileIOException
		}
	}

	d, ok := f.(fs.ReadDirFile)
	if !ok {
		_ = f.Close()
		return 0, forth.ErrInvalidPath
	}

	here := g.dataSpace.Reserved()
	g.dataSpace.Reserve(2)
	g.dataSpace.Store(here, d)
	g.dataSpace.Store(here+1, 0)
	return here, 0
}

func (g *GoSyscall) ReadDir(handle, buffer, limit int) (int, int, int) {
	d := g.dataSpace.Fetch(handle).(fs.ReadDirFile)
	infos, err := d.ReadDir(1)
	if err != nil {
		if err == io.EOF {
			return 0, 0, 0
		}
		return 0, 0, forth.ErrFileIOException
	} // len(infos) must > 0
	info := infos[0]
	var overflow = false
	for i, ch := range info.Name() {
		if i > limit {
			overflow = true
			break
		}
		g.dataSpace.Store(buffer+i, ch)
	}
	if overflow {
		return limit, -1, forth.ErrNameTooLong
	}
	return len(info.Name()), -1, 0
}

func (g *GoSyscall) Close(handle int) int {
	d := g.dataSpace.Fetch(handle).(fs.ReadDirFile)
	err := d.Close()
	if err != nil {
		g.dataSpace.Store(handle+1, forth.ErrFileIOException)
		return forth.ErrFileIOException
	}
	g.dataSpace.Store(handle+1, 1) // closed
	return 0
}

var _ forth.Syscall = &GoSyscall{}
